<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CH9329串口通信工具(十进制输入版)</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
        }
        button {
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 10px 20px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 10px 2px;
            cursor: pointer;
            border-radius: 4px;
        }
        button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        #status {
            margin: 20px 0;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            min-height: 20px;
        }
        .success {
            color: #4CAF50;
        }
        .error {
            color: #f44336;
        }
        .data-display {
            font-family: monospace;
            background-color: #f5f5f5;
            padding: 10px;
            border-radius: 4px;
            margin: 10px 0;
        }
        .input-group {
            margin: 15px 0;
        }
        label {
            display: inline-block;
            width: 120px;
        }
        input {
            padding: 8px;
            width: 60px;
            text-align: center;
        }
        .hex-preview {
            font-size: 1.2em;
            font-weight: bold;
            color: #2c3e50;
        }
        .highlight {
            background-color: #fffacd;
            padding: 0 2px;
        }
        .hex-value {
            color: #1e88e5;
            font-weight: bold;
            margin-left: 5px;
        }
        .input-container {
            display: flex;
            align-items: center;
        }
    </style>
</head>
<body>
    <h1>CH9329串口通信工具</h1>
    <p>此工具用于通过串口向CH9329模块发送自定义数据，自动计算校验位。</p>
    
    <div class="input-group">
        <label for="value1">第一个数值:</label>
        <div class="input-container">
            <input type="number" id="value1" value="100" min="0" max="255" title="请输入0-255的十进制数">
            <span class="hex-value" id="hexValue1">(0x64)</span>
        </div>
    </div>
    
    <div class="input-group">
        <label for="value2">第二个数值:</label>
        <div class="input-container">
            <input type="number" id="value2" value="100" min="0" max="255" title="请输入0-255的十进制数">
            <span class="hex-value" id="hexValue2">(0x64)</span>
        </div>
    </div>
    
    <div class="input-group">
        <label>完整数据帧:</label>
        <div id="dataPreview" class="hex-preview">
            57 AB 00 05 05 01 00 <span class="highlight">64</span> <span class="highlight">64</span> 00 <span class="highlight">D5</span>
        </div>
    </div>
    
    <button id="connectBtn">连接串口</button>
    <button id="sendBtn" disabled>发送数据</button>
    <button id="disconnectBtn" disabled>断开连接</button>
    
    <h2>状态</h2>
    <div id="status">未连接</div>
    
    <h2>接收数据</h2>
    <div id="receivedData" class="data-display">暂无数据</div>
    
    <script>
        let port;
        let reader;
        
        // 获取DOM元素
        const connectBtn = document.getElementById('connectBtn');
        const sendBtn = document.getElementById('sendBtn');
        const disconnectBtn = document.getElementById('disconnectBtn');
        const statusDiv = document.getElementById('status');
        const receivedDataDiv = document.getElementById('receivedData');
        const value1Input = document.getElementById('value1');
        const value2Input = document.getElementById('value2');
        const hexValue1 = document.getElementById('hexValue1');
        const hexValue2 = document.getElementById('hexValue2');
        const dataPreview = document.getElementById('dataPreview');
        
        // 连接串口按钮点击事件
        connectBtn.addEventListener('click', async () => {
            try {
                if (!('serial' in navigator)) {
                    throw new Error('您的浏览器不支持Web Serial API。请使用Chrome 89+或Edge 89+。');
                }
                
                port = await navigator.serial.requestPort();
                await port.open({ baudRate: 9600 });
                
                updateStatus('串口已连接', 'success');
                connectBtn.disabled = true;
                sendBtn.disabled = false;
                disconnectBtn.disabled = false;
                
                readData();
                
            } catch (error) {
                updateStatus(`错误: ${error.message}`, 'error');
                console.error('连接错误:', error);
            }
        });
        
        // 发送数据按钮点击事件
        sendBtn.addEventListener('click', async () => {
            if (!port) {
                updateStatus('请先连接串口', 'error');
                return;
            }
            
            try {
                // 获取输入值并转换为数字
                const val1 = parseInt(value1Input.value);
                const val2 = parseInt(value2Input.value);
                
                if (isNaN(val1) || isNaN(val2) || val1 < 0 || val1 > 255 || val2 < 0 || val2 > 255) {
                    throw new Error('请输入0-255之间的有效数值');
                }
                
                // 构建基本数据帧(不含校验位)
                const baseData = [
                    0x57, 0xAB, 0x00, 0x05, 0x05, 0x01, 0x00, 
                    val1, val2, 0x00
                ];
                
                // 计算校验位
                const checksum = calculateChecksum(baseData);
                const fullData = [...baseData, checksum];
                
                // 更新预览
                updateDataPreview(val1, val2, checksum);
                
                // 发送数据
                const writer = port.writable.getWriter();
                await writer.write(new Uint8Array(fullData));
                updateStatus(`数据已发送: ${arrayToHexString(fullData)}`, 'success');
                
                writer.releaseLock();
                
            } catch (error) {
                updateStatus(`发送错误: ${error.message}`, 'error');
                console.error('发送错误:', error);
            }
        });
        
        // 断开连接按钮点击事件
        disconnectBtn.addEventListener('click', async () => {
            try {
                if (reader) {
                    await reader.cancel();
                    reader = null;
                }
                
                if (port) {
                    await port.close();
                    port = null;
                }
                
                updateStatus('串口已断开', 'info');
                connectBtn.disabled = false;
                sendBtn.disabled = true;
                disconnectBtn.disabled = true;
                
            } catch (error) {
                updateStatus(`断开连接错误: ${error.message}`, 'error');
                console.error('断开连接错误:', error);
            }
        });
        
        // 输入框变化时更新预览
        value1Input.addEventListener('input', updatePreview);
        value2Input.addEventListener('input', updatePreview);
        
        // 更新16进制显示和预览
        function updatePreview() {
            // 确保数值在0-255范围内
            let val1 = parseInt(value1Input.value) || 0;
            let val2 = parseInt(value2Input.value) || 0;
            
            val1 = Math.min(255, Math.max(0, val1));
            val2 = Math.min(255, Math.max(0, val2));
            
            value1Input.value = val1;
            value2Input.value = val2;
            
            // 更新16进制显示
            hexValue1.textContent = `(0x${val1.toString(16).padStart(2, '0').toUpperCase()})`;
            hexValue2.textContent = `(0x${val2.toString(16).padStart(2, '0').toUpperCase()})`;
            
            // 计算校验位
            const baseData = [
                0x57, 0xAB, 0x00, 0x05, 0x05, 0x01, 0x00, 
                val1, val2, 0x00
            ];
            const checksum = calculateChecksum(baseData);
            
            updateDataPreview(
                val1.toString(16).padStart(2, '0').toUpperCase(),
                val2.toString(16).padStart(2, '0').toUpperCase(),
                checksum.toString(16).padStart(2, '0').toUpperCase()
            );
        }
        
        // 更新数据显示
        function updateDataPreview(val1Hex, val2Hex, checksumHex) {
            dataPreview.innerHTML = `
                57 AB 00 05 05 01 00 <span class="highlight">${val1Hex}</span> <span class="highlight">${val2Hex}</span> 00 <span class="highlight">${checksumHex}</span>
            `;
        }
        
        /**
         * 计算校验和(所有字节相加取最后两位)
         */
        function calculateChecksum(dataArray) {
            let sum = 0;
            for (const byte of dataArray) {
                sum += byte;
            }
            return sum & 0xFF; // 取最后8位(1字节)
        }
        
        // 读取串口数据
        async function readData() {
            try {
                while (port && port.readable) {
                    reader = port.readable.getReader();
                    
                    try {
                        while (true) {
                            const { value, done } = await reader.read();
                            if (done) break;
                            
                            const hexStr = arrayToHexString(Array.from(value));
                            receivedDataDiv.textContent = hexStr;
                            console.log('接收到数据:', hexStr);
                        }
                    } catch (readError) {
                        console.error('读取错误:', readError);
                    } finally {
                        reader.releaseLock();
                    }
                }
            } catch (error) {
                updateStatus(`读取数据错误: ${error.message}`, 'error');
                console.error('读取数据错误:', error);
            }
        }
        
        // 更新状态显示
        function updateStatus(message, type = 'info') {
            statusDiv.textContent = message;
            statusDiv.className = type;
        }
        
        // 将数组转换为16进制字符串
        function arrayToHexString(arr) {
            return arr.map(byte => byte.toString(16).padStart(2, '0').toUpperCase()).join(' ');
        }
        
        // 初始化预览
        updatePreview();
    </script>
</body>
</html>